package com.ochnas.volunteersignup.domain;

import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;

@Entity
public class Event implements Serializable {
	@Transient
	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd/yyyy");
	
	@Transient
	private static Comparator<Position> sortByAscendingName = new Comparator<Position>() {

		@Override
		public int compare(Position p1, Position p2) {
			String p1Name = p1.getName();
			String p2Name = p2.getName();
			return p1Name.compareTo(p2Name);
		}
		
	};
	
	
	@Id @GeneratedValue
	private Long id = null;
	@Column(name = "NAME", nullable = false)
	private String name;
	private String description;
	
	@ManyToOne (targetEntity = com.ochnas.volunteersignup.domain.User.class)
	@JoinColumn(name = "USER_ID", nullable = false) 
	private User coordinator;
	
	@Temporal(TemporalType.DATE)
	@Column(name = "START_DATE", nullable = false)
	private Date startDate;
	@Temporal(TemporalType.DATE)
	@Column(name = "END_DATE", nullable = false)
	private Date endDate;	
	@Embedded
	@AttributeOverrides({
		@AttributeOverride(name = "hourOfDay",
				           column = @Column(name = "START_TIME_HOUR_OF_DAY")),
		@AttributeOverride(name = "minutes",
						           column = @Column(name = "START_TIME_MINUTES"))				            
	})
	private Time startTime;
	@Embedded
	@AttributeOverrides({
		@AttributeOverride(name = "hourOfDay",
				           column = @Column(name = "END_TIME_HOUR_OF_DAY")),
		@AttributeOverride(name = "minutes",
						           column = @Column(name = "END_TIME_MINUTES"))				            
	})
	private Time endTime;
	
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "event")
    private List<Position> positions = new ArrayList<Position>();
	
	public Event() {
		super();
	}
	
	
	public Event(String name, String description, User coordinator, Date startDate, Date endDate, Time startTime, Time endTime) {
		super();
		this.name = name;
		this.description = description;
		this.coordinator = coordinator;
		this.startDate = startDate;
		this.endDate = endDate;
		this.startTime = startTime;
		this.endTime = endTime;
	}
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	public User getCoordinator() {
		return coordinator;
	}
	public void setCoordinator(User coordinator) {
		this.coordinator = coordinator;
	}
	public Date getStartDate() {
		return startDate;
	}
	public void setStartDate(Date startDate) {
		this.startDate = startDate;
	}
	public Date getEndDate() {
		return endDate;
	}
	public void setEndDate(Date endDate) {
		this.endDate = endDate;
	}
	public Time getStartTime() {
		return startTime;
	}
	public void setStartTime(Time startTime) {
		this.startTime = startTime;
	}
	public Time getEndTime() {
		return endTime;
	}
	public void setEndTime(Time endTime) {
		this.endTime = endTime;
	}
	
	/**
	 * Method to add position
	 * @param p - Position
	 */	
	public Position addPosition(Position p) {
		p.setEvent(this);
		positions.add(p);
		return p;
	}
	
	/**
	 * Method to remove a position from the list of positions.
	 * @param position - Position
	 * @return boolean - true, if removal is successful; false, otherwise.
	 */
	public boolean removePosition(Position position) {
		return positions.remove(position);
	}
	
	/**
	 * Method to return list of positions.
	 * @return Set<Position>
	 */
	public List<Position> getPositions() {
		Position[] posArray = positions.toArray(new Position[0]);
		Arrays.sort(posArray, sortByAscendingName);
		return Collections.unmodifiableList(Arrays.asList(posArray));
	}

	@Override
	public String toString() {
		
		StringBuffer strBuff = new StringBuffer("Event[id=");
		strBuff.append(id);
		strBuff.append("]{name=");
		strBuff.append(name);
		strBuff.append(",description=");
		strBuff.append(description);
		strBuff.append(",coordinator=");
		strBuff.append(coordinator);
		strBuff.append(",startDate=");
		if (null != startDate) {
			strBuff.append(simpleDateFormat.format(startDate));
		} else {
			strBuff.append(startDate);
		}
		strBuff.append(",endDate=");
		if (null != endDate) {
			strBuff.append(simpleDateFormat.format(endDate));
		} else {
			strBuff.append(endDate);
		}
		strBuff.append(",startTime=");
		strBuff.append(startTime);
		strBuff.append(",endTime=");
		strBuff.append(endTime);
		strBuff.append("}");
		return strBuff.toString();
	}


	public void setPositions(List<Position> positions) {
		this.positions = positions;
	}
	
	
	public String toXml() {
	       Document response = DocumentFactory.getInstance().createDocument();
	        Element eventElement = response.addElement("event");
	        eventElement.addAttribute("id", String.valueOf(getId()));
	        eventElement.addElement("name")
	        	.addText(getName());
	        eventElement.addElement("description")
	        	.addText(getDescription());
	        Element startDateElement = eventElement.addElement("date")
	        	.addAttribute("name", "startDate");
	        startDateElement.addText(simpleDateFormat.format(getStartDate()));
	        Element endDateElement = eventElement.addElement("date")
	        	.addAttribute("name", "endDate");
	        endDateElement.addText(simpleDateFormat.format(getEndDate()));
	        
	        Element eventStartTime = eventElement.addElement("time")
	        	.addAttribute("name", "startTime");
			eventStartTime.addElement("hourOfDay")
				.addText(String.valueOf(getStartTime().getHourOfDay()));
			eventStartTime.addElement("minutes")
				.addText(String.valueOf(getStartTime().getMinutes()));
			
			Element eventEndTime = eventElement.addElement("time")
				.addAttribute("name", "endTime");
			eventEndTime.addElement("hourOfDay")
				.addText(String.valueOf(getEndTime().getHourOfDay()));
			eventEndTime.addElement("minutes")
				.addText(String.valueOf(getEndTime().getMinutes()));
			
	        Element coordinator = eventElement.addElement("user")
	        	.addAttribute("role", "coordinator")
	        	.addAttribute("id", String.valueOf(getCoordinator().getId()));
	        
	        for(Position p: positions) {
	            Element positionElement = eventElement.addElement("position");
	            positionElement.addAttribute("id", String.valueOf(p.getId()));
	            positionElement.addElement("name")
	            	.addText(p.getName());
	            positionElement.addElement("description")
	            	.addText((p.getDescription()==null?"":p.getDescription()));
	            positionElement.addElement("requiredNumberOfVolunteers")
	            	.addText(String.valueOf(p.getRequiredNumberOfVolunteers()));
	            
	            Element posEventElement = positionElement.addElement("event")
	            	.addAttribute("id", String.valueOf(p.getEvent().getId()));
	            
	            for (TimeSlot t: p.getTimeSlots()) {
	            	Element timeSlotElement = positionElement.addElement("timeslot")
	            		.addAttribute("id", String.valueOf(t.getId()));
	            	timeSlotElement.addElement("quantityOfVolunteers")
	            		.addText(String.valueOf(t.getQuantityOfVolunteers()));
	        		Element tsPosition = timeSlotElement.addElement("position")
	        			.addAttribute("id", String.valueOf(t.getPosition().getId()));
	        		Element startTime = timeSlotElement.addElement("time")
	        			.addAttribute("name", "startTime");
	        		startTime.addElement("hourOfDay")
	        			.addText(String.valueOf(t.getStartTime().getHourOfDay()));
	        		startTime.addElement("minutes")
	        			.addText(String.valueOf(t.getStartTime().getMinutes()));
	        		
	        		Element endTime = timeSlotElement.addElement("time")
	        			.addAttribute("name", "endTime");
	        		endTime.addElement("hourOfDay")
	        			.addText(String.valueOf(t.getEndTime().getHourOfDay()));
	        		endTime.addElement("minutes")
	        			.addText(String.valueOf(t.getEndTime().getMinutes()));
	            }
	       
	        }
	        
	        return response.asXML();	
	}
	
	public static Event fromXml(String xml) {
		try {
			Document doc = DocumentHelper.parseText(xml);
			
			// Parse for coordinator
			Node cNode = doc.selectSingleNode("/event/user[@role='coordinator']");
			long cId = Long.parseLong(cNode.valueOf("@id"));
			
			User coordinator = new User();
			coordinator.setId(cId);

			// Parse for start time
			Node stNode = doc.selectSingleNode("/event/time[@name='startTime']");
			Node stHourOfDayNode = stNode.selectSingleNode("hourOfDay");
			int stHourOfDay = Integer.parseInt(stHourOfDayNode.getText());
			Node stMinutesNode = stNode.selectSingleNode("minutes");
			int stMinutes = Integer.parseInt(stMinutesNode.getText());
			Time startTime = new Time(stHourOfDay, stMinutes);

			// Parse for end time
			Node etNode = doc.selectSingleNode("/event/time[@name='endTime']");
			Node etHourOfDayNode = etNode.selectSingleNode("hourOfDay");
			int etHourOfDay = Integer.parseInt(etHourOfDayNode.getText());
			Node etMinutesNode = etNode.selectSingleNode("minutes");
			int etMinutes = Integer.parseInt(etMinutesNode.getText());
			Time endTime = new Time(etHourOfDay, etMinutes);
		
			// Parse for basic event information
			long eId = Long.parseLong(doc.selectSingleNode("/event/@id").getStringValue());
			String eName = doc.selectSingleNode("/event/name").getText();
			String eDescription = doc.selectSingleNode("/event/description").getText();
			Date startDate = simpleDateFormat.parse(doc.selectSingleNode("/event/date[@name='startDate']").getText());
			Date endDate = simpleDateFormat.parse(doc.selectSingleNode("/event/date[@name='endDate']").getText());
		
			
			Event e = new Event(eName, eDescription, coordinator, startDate, endDate, startTime, endTime);
			e.setId(eId);
			
			// Parse for positions and etc.
			List pList = doc.selectNodes("/event/position");
	        for (Iterator iter1 = pList.iterator(); iter1.hasNext(); ) {
				
		        Node pNode = (Node) iter1.next();
				long pId = Long.parseLong(pNode.valueOf("@id"));
				Node pName = pNode.selectSingleNode("name");
				String pNameTxt = pName.getText();
				Node pDescription = pNode.selectSingleNode("description");
				String pDescriptionTxt = pDescription.getText();
				Node rnvNode = pNode.selectSingleNode("requiredNumberOfVolunteers");
				int prequiredNumberOfVolunteers = Integer.parseInt(
						rnvNode.getText());

				Position p = new Position(pNameTxt, pDescriptionTxt,
						prequiredNumberOfVolunteers);
				p.setId(pId);
				p.setEvent(e); // set event - only contains event id
				
				// Parse for position's time slots
				List tsList = pNode.selectNodes("/position/timeslot");
				for (Iterator iter2 = tsList.iterator(); iter2.hasNext();) {
					Node tsNode = (Node) iter2.next();
					// get time slot's id
					long tsId = Long.parseLong(tsNode.valueOf("@id"));
					// get time slot's start time
					Node tsStNode = tsNode
							.selectSingleNode("time[@name='startTime']");
					Node tsStHourOfDayNode = tsStNode.selectSingleNode("hourOfDay");
					int tsStHourOfDay = Integer.parseInt(tsStHourOfDayNode.getText());
					Node tsStMinutesNode = tsStNode.selectSingleNode("minutes");
					int tsStMinutes = Integer.parseInt(tsStMinutesNode.getText());
					Time tsStTime = new Time(tsStHourOfDay, tsStMinutes);
					// get time slot's end time
					Node tsEtNode = tsNode
							.selectSingleNode("time[@name='endTime']");
					
					Node tsEtHourOfDayNode = tsEtNode.selectSingleNode("hourOfDay");
					int tsEtHourOfDay = Integer.parseInt(tsEtHourOfDayNode.getText());
					Node tsEtMinutesNode = tsEtNode.selectSingleNode("minutes");
					int tsEtMinutes = Integer.parseInt(tsEtMinutesNode.getText());
					Time tsEtTime = new Time(tsEtHourOfDay, tsEtMinutes);
					
					// get time slot's qty of volunteers
					Node qvNode = tsNode.selectSingleNode("quantityOfVolunteers");
					int qtyOfVolunteers = Integer.parseInt(qvNode.getText());

					TimeSlot ts = new TimeSlot(tsStTime, tsEtTime, qtyOfVolunteers);
					ts.setId(tsId);

					// add time slot to position
					p.addTimeSlot(ts);
				}
	            
	            // add position to event
	            e.addPosition(p);
	        }

			
			
			return e;
		
		} catch (DocumentException e) {
			throw new RuntimeException("Invalid event xml: " + xml, e);
		} catch (ParseException e) {
			throw new RuntimeException("Invalid event xml: " + xml, e);
		} catch (NumberFormatException e) {
			throw new RuntimeException("Invalid event xml: " + xml, e);
		} catch (Exception e) {
			throw new RuntimeException("Invalid event xml: " + xml, e);
		}
		
	}
}
